package realm;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import authz.CustomSimpleAuthorizationInfo;

/**
 * Realm是一个安全组件，<b>可以访问特定于应用程序的安全实体，如用户、角色和权限，以确定认证和授权操作</b>
 * <p>
 * Realms通常与数据源（如关系数据库、文件系统或其他类似资源）有1对1的对应关系
 * <p>
 * 大多数这些数据源通常包含主体（又称用户）信息，如用户名和密码，Realm可以作为PAM配置中的一个可插入的认证模块
 * <p>
 * AuthorizingRealm 通过添加授权（访问控制）支持扩展了 AuthenticatingRealm 的能力
 * <p>
 * 这是重写doGetAuthorizationInfo和doGetAuthenticationInfo两个方法的自定义realm
 * 
 * @author fidel
 *
 */
public class CustomRealm extends AuthorizingRealm {
    /**
     * <b>获取授权信息</b>时的操作
     * <p>
     * 从底层数据存储中为给定的委托人检索AuthorizationInfo
     * <p>
     * 当从这个方法返回一个实例时，你可能要考虑使用SimpleAuthorizationInfo的实例，因为它在大多数情况下是合适的
     * 
     * @param principals 应被检索的授权信息的主要识别原则
     * @return 与该委托人相关的授权信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("调用doGetAuthorizationInfo方法");
        /*
         * 返回在整个应用中用于唯一识别拥有账户/主体的主要负责人
         * 该值通常总是一个唯一的识别属性，特定于检索帐户数据的数据源。示例：
         * 一个UUID
         * 一个长值，如关系数据库中的代理主键
         * 一个LDAP UUID或静态DN
         * 一个在所有用户账户中唯一的字符串用户名
         */
        System.out.println("principals的类型：" + principals.getClass().getName());
        List<?> list = principals.asList();
        System.out.println("将principals转化为有序集合，迭代其中的元素");
        for (Object e : list) {
            System.out.println(e);
        }
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("principals的primary principal值：" + primaryPrincipal);
        // 模拟根据用户名查询权限
        Set<String> permissions = new HashSet<>();
        permissions.add("resrc1:perm1:inst1");
        permissions.add("resrc1:perm1");
        permissions.add("resrc1:perm2");
        permissions.add(":perm2");
        permissions.add("resrc2:");
        permissions.add("resrc2:perm2:inst2");
        permissions.add("resrc1:perm2");
        /*
         * AuthorizationInfo接口的简单POJO实现，将角色和权限作为内部属性存储
         */
        SimpleAuthorizationInfo info = new CustomSimpleAuthorizationInfo();
        /*
         * 添加（分配）多个权限给那些直接与账户相关的权限
         * 如果该账户还没有任何基于字符串的权限，一个新的权限集合（一个Set<String>）将被自动创建
         */
        for (Iterator<String> it = permissions.iterator(); it.hasNext();) {
            String permission = it.next();
            info.addStringPermission(permission);
        }
        Set<String> permissionSet = info.getStringPermissions();
        System.out.println("权限：");
        for (Iterator<String> it = permissionSet.iterator(); it.hasNext();) {
            String permission = it.next();
            System.out.println(permission.toString());
        }
        return info;
    }

    /**
     * 从特定的数据源（RDBMS、LDAP等）为给定的认证令牌<b>检索认证数据</b>
     * <p>
     * 在一些系统中，这个方法除了检索数据外，实际上还可以执行EIS特定的登录逻辑，这取决于Realm的实现
     * <p>
     * 一个空的返回值意味着没有账户可以与指定的令牌关联
     * 
     * @return 一个包含账户数据的认证信息对象，只有在查询成功的情况下（即账户存在且有效，等等），才会产生认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("调用doGetAuthenticationInfo方法");
        // 获取认证过程中提交的账户身份
        String principal = (String) token.getPrincipal();
        /*以账户身份为参数，从数据库中查询单行记录
        对查询得到的单行记录进行校验，如果部分字段的值不符合要求则返回null*/
        if (!principal.equals("alpha")) {
            return null;
        }
        // 如果用户名存在，那么模拟从数据库中查询对应用户名的密码，以及返回验证信息
        String password = "first";
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, password, this.getName());
        return info;
    }

}
